package library.http;

import android.text.TextUtils;

import java.io.File;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import library.http.annotations.Accept;
import library.http.annotations.BaseUrl;
import library.http.annotations.Cache;
import library.http.annotations.ContentType;
import library.http.annotations.Debuggable;
import library.http.annotations.Field;
import library.http.annotations.FormUrlEncoded;
import library.http.annotations.GET;
import library.http.annotations.Header;
import library.http.annotations.Headers;
import library.http.annotations.Multipart;
import library.http.annotations.Param;
import library.http.annotations.Params;
import library.http.annotations.Part;
import library.http.annotations.Path;
import library.http.annotations.Query;
import library.http.annotations.Timeout;
import library.http.serializable.Serialize;
import library.http.serializable.SerializeName;
import library.http.serializable.SerializeParams;


class MethodCache {

    private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_.-]*";
    private static final Pattern PARAM_PATH_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
    private static final Pattern PARAM_NAME_REGEX = Pattern.compile("^" + PARAM + "$");

    private HttpTaskProxy proxyHttpTask;

    private final HttpMethod method;
    private final String endpoint;
    private final Type returnType;
    private final Multipart multipart;
    private final FormUrlEncoded formUrlEncoded;

    private final Annotation[][] pAnnotations;

    public MethodCache(Method method, HttpTaskProxy httpTask) throws Exception {

        Annotation[] mAnnotations = method.getDeclaredAnnotations();
        HttpMethod requestMethod = null;
        String url = null;
        Multipart multipart = null;
        FormUrlEncoded formUrlEncoded = null;

        for (Annotation annotation : mAnnotations) {
            HttpMethod httpMethod = HttpMethod.valueOf(annotation);
            if (httpMethod != null) {
                try {
                    Method m = annotation.getClass().getDeclaredMethod("value");
                    url = (String) m.invoke(annotation);
                } catch (Exception ignore) {
                }
                if (requestMethod != null)
                    error("重复的请求方式注解：@%s(\"%s\")", requestMethod.name(), url);
                requestMethod = httpMethod;
            } else if (annotation instanceof Debuggable) {
                Debuggable debuggable = (Debuggable) annotation;
                httpTask.debuggable(debuggable.value());
            } else if (annotation instanceof BaseUrl) {
                BaseUrl baseUrl = (BaseUrl) annotation;
                httpTask.baseUrl(baseUrl.value());
            } else if (annotation instanceof FormUrlEncoded) {
                if (multipart != null)
                    error("@%s 与 @%s 不允许同时使用", FormUrlEncoded.class.getSimpleName(), Multipart.class.getSimpleName());
                formUrlEncoded = (FormUrlEncoded) annotation;
                httpTask.formUrlEncoded();
            } else if (annotation instanceof Multipart) {
                if (formUrlEncoded != null)
                    error("@%s 与@%s 不允许同时使用", Multipart.class.getSimpleName(), FormUrlEncoded.class.getSimpleName());
                multipart = (Multipart) annotation;
                httpTask.multipart(multipart.type());
            } else if (annotation instanceof Headers) {
                Headers header = (Headers) annotation;
                String[] keys = header.key();
                String[] values = header.value();
                if (keys.length != values.length)
                    error("@%s 注解的头信息,键值对必须对应 ", Headers.class.getSimpleName());
                for (int i = 0; i < keys.length; i++) {
                    String key = keys[i];
                    String value = values[i];
                    if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value))
                        continue;
                    httpTask.header(key, value);
                }
            } else if (annotation instanceof Params) {
                Params param = (Params) annotation;
                String[] keys = param.key();
                String[] values = param.value();
                if (keys.length != values.length)
                    error("@%s 注解的参数,键值对必须对应 ", Params.class.getSimpleName());
                for (int i = 0; i < keys.length; i++) {
                    String key = keys[i];
                    String value = values[i];
                    if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value))
                        continue;
                    httpTask.param(key, value);
                }
            } else if (annotation instanceof ContentType) {
                ContentType contentType = (ContentType) annotation;
                httpTask.header("Content-Type", contentType.value());
            } else if (annotation instanceof Accept) {
                Accept accept = (Accept) annotation;
                httpTask.header("Accept", accept.value());
            } else if (annotation instanceof Cache) {
                Cache cache = (Cache) annotation;
                httpTask.cache(cache.value());
            } else if (annotation instanceof Timeout) {
                Timeout timeout = (Timeout) annotation;
                httpTask.timeout(timeout.connect(), timeout.read(), timeout.write());
            }
        }


        this.proxyHttpTask = httpTask;

        this.returnType = method.getGenericReturnType();
        this.method = requestMethod != null ? requestMethod : HttpMethod.GET;
        this.endpoint = url;
        this.multipart = multipart;
        this.formUrlEncoded = formUrlEncoded;

        this.pAnnotations = method.getParameterAnnotations();
    }

    public synchronized HttpTask parse(Object... args) throws Exception {
        return parseArgs(proxyHttpTask.newInstance(), args);
    }

    private synchronized HttpTask parseArgs(HttpTask httpTask, Object... args) throws Exception {
        Map<String, String> headers = new LinkedHashMap<>();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (TextUtils.isEmpty(key)
                    || TextUtils.isEmpty(value))
                continue;
            httpTask.header(key, value);
        }

        return parseParams(httpTask, args);
    }


    private synchronized HttpTask parseParams(HttpTask httpTask, Object[] args) throws Exception {
        final List<KeyValue<String, String>> mPaths = new ArrayList<>();
        for (int index = 0; index < pAnnotations.length; index++) {
            Annotation[] array = pAnnotations[index];
            Object arg = args[index];
            if (arg instanceof Download) {
                if (httpTask.getDownload() == null)
                    httpTask.download((Download<?>) arg);
                else {
                    error("定义了多个Download参数 @index = %d", index);
                }
                continue;
            }
            if (array == null || array.length == 0) {
                error("无效(无注解)的参数 @index = %d: %s", index, arg);
            }
            for (int offset = 0; offset < array.length; offset++) {
                Annotation annotation = array[offset];
                if (annotation instanceof Param) {
                    Param param = (Param) annotation;
                    httpTask.param(param.value(), arg != null ? String.valueOf(arg) : null);
                } else if (annotation instanceof Path) {
                    Path path = (Path) annotation;
                    if (!PARAM_NAME_REGEX.matcher(path.value()).matches()) {
                        error("@%s(\"%s\") 注解的值不符合%s", Path.class.getSimpleName(), path.value(), PARAM_NAME_REGEX.pattern());
                    }
                    String query = "{" + path.value() + "}";
                    if (endpoint == null || !endpoint.contains(query)) {
                        error("未发现 @%s(\"%s\") 指向的路径\"", Path.class.getSimpleName(), path.value());
                    }
                    mPaths.add(new KeyValue<>(path.value(), arg != null ? String.valueOf(arg) : null));
                } else if (annotation instanceof Query) {
                    if (method != null && method != HttpMethod.GET) {
                        error("@%s 注解只适用于 @%s 方法注解", Query.class.getSimpleName(), GET.class.getSimpleName());
                    }
                    Query query = (Query) annotation;
                    if (!PARAM_NAME_REGEX.matcher(query.value()).matches()) {
                        error("@%s 注解的值必须符合 %s", Query.class.getSimpleName(), PARAM_NAME_REGEX.pattern());
                    }
                    httpTask.query(query.value(), arg != null ? String.valueOf(arg) : null);
                } else if (annotation instanceof Field) {
                    if (multipart != null || formUrlEncoded == null) {
                        error("@%s 注解只适用于 @%s 方法注解", Field.class.getSimpleName(), FormUrlEncoded.class.getSimpleName());
                    }
                    Field field = (Field) annotation;
                    if (!PARAM_NAME_REGEX.matcher(field.value()).matches()) {
                        error("@%s 注解的值必须符合 %s", Field.class.getSimpleName(), PARAM_NAME_REGEX.pattern());
                    }
                    httpTask.field(field.value(), arg != null ? arg != null ? String.valueOf(arg) : null : null, field.encoded());
                } else if (annotation instanceof Part) {
                    if (formUrlEncoded != null || multipart == null) {
                        error("@%s 注解只适用于 @%s 方法注解", Part.class.getSimpleName(), Multipart.class.getSimpleName());
                    }
                    Part part = (Part) annotation;
                    if (!PARAM_NAME_REGEX.matcher(part.value()).matches()) {
                        error("@%s 注解的值必须符合 %s", Part.class.getSimpleName(), PARAM_NAME_REGEX.pattern());
                    }
                    String name = part.value();
                    if (arg instanceof FilePart) {
                        FilePart filePart = (FilePart) arg;
                        httpTask.part(name, filePart);
                    } else if (arg instanceof String) {
                        httpTask.part(name, (String) arg);
                    } else if (arg instanceof File) {
                        File file = (File) arg;
                        if (file != null && file.exists()) {
                            httpTask.part(name, file);
                        }
                    } else {
                        error("@%s 注解的参数必须为 %s,%s,%s 类型", Part.class.getSimpleName(),
                                FilePart.class.getSimpleName(),
                                String.class.getSimpleName(),
                                File.class.getSimpleName());
                    }
                } else if (annotation instanceof Header) {
                    Header header = (Header) annotation;
                    httpTask.header(header.value(), arg != null ? String.valueOf(arg) : null);
                } else if (annotation instanceof SerializeParams) {
                    SerializeParams params = (SerializeParams) annotation;
                    Map<String, Object> map = convertSerializeParams2Map(params, arg);
                    if (map != null)
                        httpTask.params(map);
                }

            }
        }

        String url = endpoint;
        Matcher matcher = PARAM_PATH_REGEX.matcher(url);
        int i = 0;
        while (matcher.find()) {
            i++;
        }

        if (i != mPaths.size()) {
            error("URL 中定义的 {PATH} 与 @Path 数量不匹配");
        }
        for (KeyValue<String, String> item : mPaths) {
            url = url.replace("{" + item.getKey() + "}", item.getValue() != null ? item.getValue() : "");
        }
        url = HttpUtils.parseUrl(httpTask.getBaseUrl(), url);
        httpTask.url(url);
        httpTask.method(method.name());
        return httpTask;
    }

    private Map<String, Object> convertSerializeParams2Map(SerializeParams params, Object arg) {
        if (params == null || arg == null)
            return null;
        try {
            final Class serializableClass = arg.getClass();
            if (serializableClass instanceof Serializable || serializableClass.getAnnotation(Serialize.class) != null) {

            } else {
                return null;
            }
            Map<String, Object> map = new LinkedHashMap<>();
            for (Class<?> clazz = arg.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
                java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
                if (fields == null || fields.length == 0)
                    continue;
                for (int j = 0; j < fields.length; j++) { // 遍历所有属性
                    try {
                        java.lang.reflect.Field field = fields[j];
                        field.setAccessible(true);
                        SerializeName serializeName = field.getAnnotation(SerializeName.class);

                        String name = field.getName(); // 获取属性的名字
                        String expose = serializeName != null ? serializeName.value() : null;
                        String key = !TextUtils.isEmpty(expose) ? expose : name;
                        Object value = field.get(arg);
                        map.put(key, value);
                    } catch (Exception e) {

                    }
                }

            }
            return map;
        } catch (Exception e) {

        }
        return null;
    }

    protected void error(String message, Object... args) throws Exception {
        throw new Exception(String.format(message, args));
    }
}
